package com.xmxsuperstar.app.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.net.ssl.SSLSocketFactory;

import net.love5z.common.utils.StringUtils;

import org.apache.log4j.Logger;

import com.xmxsuperstar.app.CommonConstants;
import com.xmxsuperstar.app.Loggers;
import com.xmxsuperstar.http.ChunkedInputStream;
import com.xmxsuperstar.http.HttpRequestMsg;
import com.xmxsuperstar.http.HttpResponseMsg;

public class SocketServer {

	private static Logger logger = Loggers.defaultLogger;

	public static void main(String[] args) {
		ServerSocket sSocket = null;
		try {
			int port = 10001;
			sSocket = new ServerSocket(port);
			sSocket.setSoTimeout(20000);

			logger.info("Socket server is listing on:=" + port);

			while (true) {
				Socket socket = null;
				try {
					socket = sSocket.accept();
				} catch (SocketTimeoutException ste) {
					continue;
				}
				if (socket != null) {
					Thread processThread = new Thread(new ServerProcessThread(
							socket));
					processThread.start();
				}
			}
		} catch (IOException e) {
			logger.error(e);
		}
	}
}

class ServerProcessThread implements Runnable {

	private static Logger logger = Loggers.defaultLogger;
	private static Logger restLogger = Loggers.restLogger;

	Socket socket;

	public ServerProcessThread(Socket socket) {
		this.socket = socket;
	}

	@Override
	public void run() {
		try {
			restLogger.info("FROM: " + socket.getRemoteSocketAddress());
			ObjectInputStream in = new ObjectInputStream(new GZIPInputStream(
					socket.getInputStream()));
			GZIPOutputStream gout = new GZIPOutputStream(
					socket.getOutputStream());
			ObjectOutputStream out = new ObjectOutputStream(gout);
			HttpRequestMsg reqMsg = (HttpRequestMsg) in.readObject();

			boolean isSecure = in.readBoolean();
			String host = (String) in.readObject();
			int port = in.readInt();

			restLogger.info("HOST: " + host);
			restLogger.info("PORT: " + port);
			restLogger.info("HTTPS: " + isSecure);
			restLogger.info("REQ: " + reqMsg);

			Socket targetSocket = this.createSocket(host, port, isSecure);
			InputStream targetIn = targetSocket.getInputStream();
			OutputStream targetOut = targetSocket.getOutputStream();

			// Begin write request data to target server
			// GET /index.htm HTTP/1.1
			writeHttpLine(targetOut, reqMsg.getMethod().getMethodName() + " "
					+ reqMsg.getPath() + " " + reqMsg.getVersion());
			// Headers
			for (String headerName : reqMsg.fetchAllHeaderNames()) {
				for (String headerValue : reqMsg.getHeader(headerName)) {
					writeHttpLine(targetOut, headerName + ": " + headerValue);
				}

			}
			// Blank Line, Seperate headers and body
			writeHttpLine(targetOut, "");

			// Write Body
			targetOut.write(reqMsg.fetchBody());

			// Flush
			targetOut.flush();
			// End write request data to target server

			// Begin read response data from target server
			HttpResponseMsg resMsg = new HttpResponseMsg();
			// HTTP/1.1 200 OK
			String line = readHttpLine(targetIn);
			resMsg.setVersion(line.split("\\s")[0]);
			resMsg.setStatusCode(line.split("\\s")[1]);

			// Headers
			while (true) {
				line = readHttpLine(targetIn);
				if (StringUtils.isBlank(line)) {
					break;
				} else {
					String headerName = line.split(":\\s")[0];
					String headerValue = line.split(":\\s")[1];

					resMsg.addHeader(headerName, headerValue);
				}
			}

			// Content
			InputStream targetContentStream = targetIn;
			if (resMsg.getHeader("Transfer-Encoding").size() > 0
					&& "chunked".equalsIgnoreCase(resMsg.getHeader(
							"Transfer-Encoding").get(0))) {
				targetContentStream = new ChunkedInputStream(
						targetContentStream);
			}

			if (resMsg.getHeader("Content-Encoding").size() > 0
					&& "gzip".equalsIgnoreCase(resMsg.getHeader(
							"Content-Encoding").get(0))) {
				targetContentStream = new GZIPInputStream(targetContentStream);
			}

			int b = -1;
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			while ((b = targetContentStream.read()) != -1) {
				bos.write(b);
			}
			resMsg.setBody(bos.toByteArray());

			restLogger.info("RES: " + resMsg);
			out.writeObject(resMsg);
			out.flush();
			gout.finish();

		} catch (IOException e) {
			logger.error(e);
		} catch (ClassNotFoundException e) {
			logger.error(e);
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				logger.error(e);
			}
		}

	}

	private void writeHttpLine(OutputStream out, String line) {
		try {
			byte[] bytes = new StringBuilder(line).append(CommonConstants.CRLF)
					.toString().getBytes(CommonConstants.CHAR_ENCODING);
			out.write(bytes);
			logger.info(line);
		} catch (UnsupportedEncodingException e) {
			logger.error(e);
		} catch (IOException e) {
			logger.error(e);
		}

	}

	private String readHttpLine(InputStream in) {
		try {
			byte[] buffer = new byte[1024];
			int index = 0;
			while (true) {
				int b = in.read();
				if (b == '\r') {
					int bb = in.read();
					if (bb == '\n') {
						// oh,fuck, we read the line
						String line = new String(buffer, 0, index,
								CommonConstants.CHAR_ENCODING);
						logger.info(line);
						return line;
					} else {
						buffer[index++] = (byte) b;
						buffer[index++] = (byte) bb;
					}
				} else {
					buffer[index++] = (byte) b;
				}

			}
		} catch (IOException e) {
			logger.error(e);
		}

		return null;

	}

	private Socket createSocket(String host, int port, boolean isSecure)
			throws IOException {
		Socket s = null;

		if (isSecure) {
			SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory
					.getDefault();
			s = sslsocketfactory.createSocket(host, port);
		} else {
			s = new Socket(host, port);
		}

		return s;
	}

}